<style scoped>
.tree-ew {
    cursor: col-resize;
    position: absolute;
    top: 0;
    right: 0;
    width: 5px;
    height: 100%;
    background-color: #383838;
}
.tree-ew-ver {
    cursor: row-resize;
    position: absolute;
    bottom: -2px;
    left: 0;
    width: 100%;
    height: 5px;
    background-color: #383838;
}

.tree {
    position: relative;
    width: 250px;
    height: 100%;
}

.prop {
    position: relative;
    width: 320px;
    height: 100%;
}

.node-hide {
    opacity: 0.3;
}

.icon-ts{
    position: absolute;
    right: 15px;
}
.icon-ts > div{
    width: 6px;
    height: 6px;
    border-radius: 3px;
}
</style>

<template>
    <div :style="{ position: 'relative', display: 'flex', flex: 1, 'flex-direction': isHor ? 'row' : 'column' }">
        <div class="tree" tabindex="0" @keyup="onKeyUp">
            <el-tree-v2 :height="treeHeight" :props="defaultProps" ref="treeRef"
                :default-expanded-keys="expandedKeys" empty-text="正在加载场景" :highlight-current="true"
                :expand-on-click-node="false" @node-expand="onNodeExpand" @node-collapse="onNodeCollapse"
                @current-change="onCurrentNodeChange">
                <template #default="{ node }">
                    <span :class="{ 'node-hide': !node.data.active }">{{ node.label }}</span>
                    <div v-if="iconProp[node.data.uuid]" class="icon-ts">
                        <div style="background-color: #73a1d0;" v-if="iconProp[node.data.uuid].self && iconProp[node.data.uuid].child"></div>
                        <div style="background-color: #CFD3DC;" v-else-if="iconProp[node.data.uuid].self"></div>
                        <div style="background-color: #656565;" v-else-if="iconProp[node.data.uuid].child"></div>
                    </div>
                </template>
            </el-tree-v2>
            <div v-show="isHor" id="tree-ew" class="tree-ew"></div>
            <div v-show="!isHor" id="tree-ew-ver" class="tree-ew-ver"></div>
        </div>
        
        <div class="prop" tabindex="1" @keyup="onKeyUpH">
            <div :style="{ height: propHeight, 'overflow-y': 'scroll', 'scrollbar-width': 'none'}">
                <TreeProp v-if="updateKey != 0 && isValid()" :node="currentNode" :update="updateKey" />
                <div v-show="isHor" id="prop-ew" class="tree-ew"></div>
            </div>
        </div>

        <div v-show="!isHor" id="prop-ew-ver" class="tree-ew"></div>
    </div>
</template>

<script setup lang="ts">
import type { ElTreeV2, TreeNode, TreeNodeData } from 'element-plus';
import { nextTick, onMounted, ref, watchEffect } from 'vue';
import TreeProp from './TreeProp.vue';

const { isHor } = defineProps<{ isHor: boolean }>()
watchEffect(() => {
    if(isHor){} //需要引用下isHor，否则侦听不到响应
    nextTick(()=>{
        if(isHor){
            treeHeight.value = window.innerHeight - 50
            propHeight.value = treeHeight.value
            resizeTree()
            resizeProp()
        }
        else{
            resizeVer()
            resizeHor()
        }
    })
})

const defaultProps = {
    value: 'uuid',
    label: 'name',
}
//@ts-ignore
const isDev = process.env.NODE_ENV === 'development'
const treeHeight = ref(isHor ? window.innerHeight - 50 : (window.innerHeight - 50)/2)
const propHeight = ref(isHor ? treeHeight : window.innerHeight - 50 - treeHeight.value)
const treeRef = ref<InstanceType<typeof ElTreeV2>>()
let treeData: Array<CCTreeNode> = []
const expandedNodeMap = new Map();
let expandedKeys: string[] = [];
const updateKey = ref(1);
let currentNode: any = null
//self: 自身挂载ts脚本， child: 子节点挂载ts脚本
const iconProp: Record<string, {self: boolean, child: boolean}> = {}

function isValid(){
    return (currentNode || isDev) && window.cc && cc.isValid(currentNode)
}

onMounted(() => {
    const intervalId = setInterval(() => {
        if (window.cc && !cc.sys.isMobile && cc.director.getScene()) {
            console.log('------cc-inspector init------');
            clearInterval(intervalId);
            refreshTree()
            resizeTree()
            resizeProp()
            resizeVer()
            resizeHor()
            setDefault()
            window.addEventListener('keydown', (event: KeyboardEvent)=>{
                if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {
                    event.preventDefault(); // 阻止默认的滚动行为
                }
            });
        }
    }, 1000)
})

//#region 重设窗口大小
function resizeTree() {
    if(!isHor) return;
    const handle: HTMLElement = document.querySelector('#tree-ew');
    if(!handle) return;
    const container: HTMLElement = document.querySelector('.tree');
    const width = localStorage.getItem('ccc_devtools_tree_width')
    if(width) container.style.width = `${Number(width)}px`;
    container.style.height = treeHeight.value + 'px'

    let isDragging = false;
    function onMouseDown(e: MouseEvent) {
        isDragging = true;
        const startX = e.clientX;
        const startWidth = container.offsetWidth;

        document.addEventListener('mousemove', onMouseMove);
        document.addEventListener('mouseup', onMouseUp);

        function onMouseMove(e: MouseEvent) {
            if (isDragging) {
                const deltaX = e.clientX - startX;
                container.style.width = `${startWidth + deltaX}px`;
            }
        }

        function onMouseUp(e: MouseEvent) {
            isDragging = false;
            document.removeEventListener('mousemove', onMouseMove);
            document.removeEventListener('mouseup', onMouseUp);
            const width = e.clientX - startX + startWidth
            localStorage.setItem('ccc_devtools_tree_width', width.toString())
        }
    }
    handle.removeEventListener('mousedown', onMouseDown)
    handle.addEventListener('mousedown', onMouseDown)
}
function resizeProp() {
    if(!isHor) return;
    const handle: HTMLElement = document.querySelector('#prop-ew');
    if(!handle) return;
    const container: HTMLElement = document.querySelector('.prop');
    const width = localStorage.getItem('ccc_devtools_prop_width')
    if(width) container.style.width = `${Number(width)}px`;

    let isDragging = false;
    function onMouseDown(e: MouseEvent) {
        isDragging = true;
        const startX = e.clientX;
        const startWidth = container.offsetWidth;

        document.addEventListener('mousemove', onMouseMove);
        document.addEventListener('mouseup', onMouseUp);

        function onMouseMove(e: MouseEvent) {
            if (isDragging) {
                const deltaX = e.clientX - startX;
                container.style.width = `${startWidth + deltaX}px`;
            }
        }

        function onMouseUp(e: MouseEvent) {
            isDragging = false;
            document.removeEventListener('mousemove', onMouseMove);
            document.removeEventListener('mouseup', onMouseUp);
            const width = e.clientX - startX + startWidth
            localStorage.setItem('ccc_devtools_prop_width', width.toString())
        }
    }
    handle.removeEventListener('mousedown', onMouseDown);
    handle.addEventListener('mousedown', onMouseDown);
}

function resizeVer() {
    if(isHor) return;
    const handle: HTMLElement = document.querySelector('#tree-ew-ver');
    if(!handle) return;
    const container: HTMLElement = document.querySelector('.tree');
    const height = localStorage.getItem('cc_tree_height')
    if(height){
        treeHeight.value = Number(height)
        propHeight.value = window.innerHeight - 50 - treeHeight.value
    }
    else{
        treeHeight.value = (window.innerHeight - 50)/2
        propHeight.value = window.innerHeight - 50 - treeHeight.value
    }
    container.style.height = treeHeight.value + 'px';

    let isDragging = false;
    function onMouseDown(e: MouseEvent) {
        isDragging = true;
        const startY = e.clientY;
        const startHeight = container.offsetHeight;

        document.addEventListener('mousemove', onMouseMove);
        document.addEventListener('mouseup', onMouseUp);

        function onMouseMove(e: MouseEvent) {
            if (isDragging) {
                const delta = e.clientY - startY;
                container.style.height = `${startHeight + delta}px`;
                treeHeight.value = startHeight + delta
                propHeight.value = window.innerHeight - 50 - treeHeight.value
            }
        }

        function onMouseUp(e: MouseEvent) {
            isDragging = false;
            document.removeEventListener('mousemove', onMouseMove);
            document.removeEventListener('mouseup', onMouseUp);
            const height = e.clientY - startY + startHeight
            localStorage.setItem('cc_tree_height', height.toString())
        }
    }
    handle.removeEventListener('mousedown', onMouseDown)
    handle.addEventListener('mousedown', onMouseDown)
}
function resizeHor() {
    if(isHor) return;
    const handle: HTMLElement = document.querySelector('#prop-ew-ver');
    if(!handle) return;
    const container: HTMLElement = document.querySelector('.prop');
    const treeContainer: HTMLElement = document.querySelector('.tree');
    const width = localStorage.getItem('ccc_devtools_prop_width')
    if(width) container.style.width = `${Number(width)}px`;
    treeContainer.style.width = container.style.width

    let isDragging = false;
    function onMouseDown(e: MouseEvent) {
        isDragging = true;
        const startX = e.clientX;
        const startWidth = container.offsetWidth;

        document.addEventListener('mousemove', onMouseMove);
        document.addEventListener('mouseup', onMouseUp);

        function onMouseMove(e: MouseEvent) {
            if (isDragging) {
                const deltaX = e.clientX - startX;
                container.style.width = `${startWidth + deltaX}px`;
                treeContainer.style.width = container.style.width
            }
        }

        function onMouseUp(e: MouseEvent) {
            isDragging = false;
            document.removeEventListener('mousemove', onMouseMove);
            document.removeEventListener('mouseup', onMouseUp);
            const width = e.clientX - startX + startWidth
            localStorage.setItem('ccc_devtools_prop_width', width.toString())
        }
    }
    handle.removeEventListener('mousedown', onMouseDown);
    handle.addEventListener('mousedown', onMouseDown);
}
//#endregion

function setDefault(){
    //默认展开并选中Canvas
    for(let i=0, len=treeData.length; i<len; i++){
        if(treeData[i].name == 'Canvas'){
            const tree = treeRef.value
            const key = treeData[i].uuid
            setTimeout(()=>{
                const node = tree.getNode(key)
                if(!node) return;
                tree.expandNode(node)
                // tree.setCurrentKey(key);
                // const ccNode = getChildByUuidPath(cc.director.getScene(), node.data.path, 0);
                // currentNode = ccNode;
            }, 100)
            break
        }
    }
}

function forEachComp(node: CCTreeNode){
    let has = false
    let prop = {self: false, child: false}
    iconProp[node.uuid] = prop
    const comps = node.components
    for(let i=0, len=comps.length; i<len; i++){
        const name: string = comps[i].__classname__
        const char = name.substring(0, name.indexOf('.'))
        if(!char || !['cc', 'sp', 'dragonBones'].includes(char)){
            prop.self = true
            has = true
            break
        }
    }
    const children = node.children
    for(let i=0, len=children.length; i<len; i++){
        if(forEachComp(children[i]))
            prop.child = true
    }
    return has
}

function refreshTree() {
    if (window.ccdevShow) {
        const children = cc.director.getScene().children
        let value: CCTreeNode[] = [];
        getChildren(value, children, []);
        treeData = value
        treeRef.value.setData(value);
        updateKey.value = -updateKey.value;

        // for (const key of Object.keys(iconProp)) {
        //     delete iconProp[key]
        // }
        for(let i=0, len=children.length; i<len; i++){
            forEachComp(children[i])
        }
    }
    window.requestAnimationFrame(refreshTree);
}

function getChildren(container: CCTreeNode[], children: CCTreeNode[], path: string[]) {
    children.forEach(ccNode => {
        const childPath = path.concat(ccNode.uuid);
        const node = {
            uuid: ccNode.uuid,
            name: ccNode.name,
            active: ccNode.activeInHierarchy,
            children: [] as CCTreeNode[],
            path: childPath,
        };
        if (ccNode.children && ccNode.children.length > 0) {
            getChildren(node.children, ccNode.children, childPath);
        }
        container.push(node);
    });
}

function onNodeExpand(data: TreeNodeData, node: TreeNode) {
    expandedNodeMap.set(data.uuid, true);
    expandedKeys = [...expandedNodeMap.keys()];
}

function onNodeCollapse(data: TreeNodeData, node: TreeNode) {
    expandedNodeMap.delete(data.uuid);
    expandedKeys = [...expandedNodeMap.keys()];
}

function onCurrentNodeChange(data: TreeNodeData, node: TreeNode) {
    // console.log('onCurrentNodeChange', data, node)
    if (data) {
        const ccNode = getChildByUuidPath(cc.director.getScene(), data.path, 0);
        currentNode = ccNode;
    } else {
        currentNode = null;
    }
}

function onKeyUp(event: KeyboardEvent){
    if (!window.ccdevShow) return;
    const tree = treeRef.value;
    if (!tree) return;
    const key = tree.getCurrentKey()
    if (!key) return;
    const node = tree.getNode(key)
    if (!node) return;
    const data = node.data as CCTreeNode

    if (event.key === 'ArrowDown') {
        event.preventDefault();
        const key = getNextNode(node)
        if (key){
            tree.setCurrentKey(key);
            const ccNode = getChildByUuidPath(cc.director.getScene(), tree.getNode(key).data.path, 0);
            currentNode = ccNode;
        }
    }
    else if (event.key === 'ArrowUp') {
        event.preventDefault();
        const key = getPrevNode(tree, node)
        if (key){
            tree.setCurrentKey(key);
            const ccNode = getChildByUuidPath(cc.director.getScene(), tree.getNode(key).data.path, 0);
            currentNode = ccNode;
        }
    }
    else if (event.key === 'ArrowRight') {
        if (node.children && !expandedNodeMap.get(data.uuid))
            tree.expandNode(node);
        else if (node.children) {
            let key;
            for (let i = 0, len = node.children.length; i < len; i++) {
                if (node.children[i].children) {
                    key = node.children[i].key
                    break
                }
            }
            if (key){
                tree.setCurrentKey(key);
                const ccNode = getChildByUuidPath(cc.director.getScene(), tree.getNode(key).data.path, 0);
                currentNode = ccNode;
            }
        }
    }
    else if (event.key === 'ArrowLeft') {
        if (node.children && expandedNodeMap.get(data.uuid))
            tree.collapseNode(node);
        else if (node.parent){
            tree.setCurrentKey(node.parent.key);
            const ccNode = getChildByUuidPath(cc.director.getScene(), tree.getNode(node.parent.key).data.path, 0);
            currentNode = ccNode;
        }
    }
    else if(event.key === 'h'){
        currentNode.active = !currentNode.active
    }
}

function onKeyUpH(event: KeyboardEvent){
    if(event.key === 'h'){
        if(currentNode)
            currentNode.active = !currentNode.active
    }
}

function getNextNode(node: TreeNodeData) {
    const data = node.data as CCTreeNode
    if (expandedNodeMap.get(node.key) && data.children.length > 0) {
        return data.children[0].uuid
    }
    else {
        let curSelect = node
        while (curSelect.parent) {
            let children = curSelect.parent.children
            if (children && children.length > 0) {
                const index = children.indexOf(curSelect)
                if (children[index + 1]) {
                    return children[index + 1].key
                }
                else curSelect = curSelect.parent;
            }
        }
        const index = treeData.indexOf(curSelect.data)
        if (treeData[index + 1])
            return treeData[index + 1].uuid
        else
            return treeData[0].uuid
    }
}

function getPrevNode(tree: InstanceType<typeof ElTreeV2>, node: TreeNodeData) {
    let curSelect = node
    if (curSelect.parent) {
        let children = curSelect.parent.children
        const index = children.indexOf(curSelect)
        if (children[index - 1])
            curSelect = children[index - 1]
        else {
            curSelect = curSelect.parent
            return curSelect.key
        }
    }
    else {
        const index = treeData.indexOf(curSelect.data)
        if (treeData[index - 1])
            curSelect = tree.getNode(treeData[index - 1].uuid)
        else
            curSelect = tree.getNode(treeData[treeData.length - 1].uuid)
    }
    while (expandedNodeMap.get(curSelect.key)) {
        curSelect = curSelect.children[curSelect.children.length - 1]
    }
    return curSelect.key
}

function getChildByUuidPath(node: any, path: string[], index: number): any {
    if (index >= path.length) {
        return node;
    }
    node = node.getChildByUuid(path[index]);
    return getChildByUuidPath(node, path, index + 1);
}
</script>